# Copyright (c) 2009 Peter Corbett
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

import random
import time
import gamestate
import selector

class LevelGen:
    def __init__(self, level, state, data, depth):
        self.level = level
        self.state = state
        self.data = data
        self.depth = depth
    def generate(self):
        floor = self.data.floordict["floor"]
        wall = self.data.floordict["wall"]
        door = self.data.floordict["door"]

        
        self.level.floortiles = [[floor for j in range(20)] for i in range(80)]
        self.regions = [[None for j in range(20)] for i in range(80)]
        self.boxes = []
        self.make_box(0, 0, 79, 19)

        wallsegs = []
        for x in range(1,79):
            wallseg = []
            for y in range(1,19):
                if self.level.floortiles[x][y] == wall and self.level.floortiles[x-1][y] == floor and self.level.floortiles[x+1][y] == floor:
                    wallseg.append((x, y))
                    #self.level.floortiles[x][y] = 2
                    r1 = self.regions[x-1][y]
                    r2 = self.regions[x+1][y]
                else:
                    if len(wallseg) > 0:
                        wallsegs.append((tuple(wallseg), r1, r2))
                        wallseg = []
            if len(wallseg) > 0:
                wallsegs.append((tuple(wallseg), r1, r2))
                wallseg = []

        for y in range(1,19):
            wallseg = []
            for x in range(1,79):
                if self.level.floortiles[x][y] == wall and self.level.floortiles[x][y-1] == floor and self.level.floortiles[x][y+1] == floor:
                    wallseg.append((x, y))
                    #self.level.floortiles[x][y] = 3
                    r1 = self.regions[x][y-1]
                    r2 = self.regions[x][y+1]
                else:
                    if len(wallseg) > 0:
                        wallsegs.append((tuple(wallseg), r1, r2))
                        wallseg = []
            if len(wallseg) > 0:
                wallsegs.append((tuple(wallseg), r1, r2))
                wallseg = []
                
        boxdict = {}
        finalboxdict = {}
        for i in self.boxes:
            boxdict[i] = set()
            finalboxdict[i] = list()
        for i in wallsegs:
            boxdict[i[1]].add((i[0], i[2]))
            boxdict[i[2]].add((i[0], i[1]))

        unvisited_boxes = set(self.boxes)
        current_box = self.boxes[0]
        unvisited_boxes.remove(current_box)
        l = [(current_box, i[0], i[1]) for i in boxdict[current_box] if i[1] in unvisited_boxes]
        random.shuffle(l)

        to_visit = []
        to_visit.extend(l)

        connections = []
        while len(to_visit) > 0:
            v = to_visit.pop()
            if not v[2] in unvisited_boxes:
                continue
            x,y = random.choice(v[1])
            self.level.floortiles[x][y] = door
            finalboxdict[v[0]].append((x, y, v[2]))
            finalboxdict[v[2]].append((x, y, v[0]))
            #connections.append(v[1])
            unvisited_boxes.remove(v[2])
            l = [(v[2], i[0], i[1]) for i in boxdict[v[2]] if i[1] in unvisited_boxes]
            random.shuffle(l)
            to_visit.extend(l)

        truerooms = list()

        for i in self.boxes:
            #print finalboxdict[i]
            if len(finalboxdict[i]) == 2:
                for x in range(i[0]+1, i[2]):
                    for y in range(i[1]+1, i[3]):
                        self.level.floortiles[x][y] = wall
                self.to_corridor(i, finalboxdict[i][0], finalboxdict[i][1])
                for j in range(2):
                    if len(finalboxdict[finalboxdict[i][j][2]]) == 2:
                        x, y, discard = finalboxdict[i][j]
                        self.level.floortiles[x][y] = floor
            else:
                truerooms.append(i)

        random.shuffle(truerooms)
        self.make_stairs(truerooms[0], True)
        if self.depth < 9:
            self.make_stairs(truerooms[1], False)

        d = {}
        for i in range(len(self.data.mobtypes)):
            m = self.data.mobtypes[i]
            if m.generate and m.rarity > 0 and (self.depth + 1) >= m.level:
                d[i] = (1.5 ** m.level) / m.rarity
        mob_sel = selector.Selector(d)

        d = {}
        for i in range(len(self.data.itemtypes)):
            itype = self.data.itemtypes[i]
            if itype.rarity > 0 and (self.depth + 1) >= itype.level:
                d[i] = (1.5 ** itype.level) / itype.rarity
        item_sel = selector.Selector(d)
                
        for i in range(10):
            (x, y) = self.level.get_mob_place(self.data)
            self.level.mobs.append(gamestate.Mob(self.state.id_maker.new_id(),x,y,mob_sel.select(),self.data,item_sel))
        if self.depth == 9:
            (x, y) = self.level.get_mob_place(self.data)
            self.level.mobs.append(gamestate.Mob(self.state.id_maker.new_id(),x,y,self.data.mobdict["boss"],self.data,item_sel))

        for i in range(10):
            (x, y) = self.level.get_mob_place(self.data)
            item = gamestate.Item(item_sel.select())
            self.level.items[x][y].append(item)
 
    def make_stairs(self, room, up):
        x = random.randint(room[0]+1, room[2]-1)
        y = random.randint(room[1]+1, room[3]-1)
        self.level.floortiles[x][y] = self.data.floordict["stairs (leading up)"] if up else self.data.floordict["stairs (leading down)"]
        (self.level.stairs_up if up else self.level.stairs_down).append((x, y))

    def to_corridor(self, room, door1, door2):
        floor = self.data.floordict["floor"]
        if door1[0] == room[0]:
            d1o = "w"
        elif door1[0] == room[2]:
            d1o = "e"
        elif door1[1] == room[1]:
            d1o = "n"
        elif door1[1] == room[3]:
            d1o = "s"
        if door2[0] == room[0]:
            d2o = "w"
        elif door2[0] == room[2]:
            d2o = "e"
        elif door2[1] == room[1]:
            d2o = "n"
        elif door2[1] == room[3]:
            d2o = "s"

        if d2o in ("e", "w") and d1o in ("n", "s"):
            door1, door2 = door2, door1
            d1o, d2o = d2o, d1o
        if d1o in ("e", "w") and d2o in ("n", "s"):
            center = door2[0], door1[1]
            for x in range(min(center[0],door1[0]+1),max(center[0],door1[0]-1)+1):
                self.level.floortiles[x][center[1]] = floor
            for y in range(min(center[1],door2[1]+1),max(center[1],door2[1]-1)+1):
                self.level.floortiles[center[0]][y] = floor
        if d1o in ("n", "s") and d2o in ("n", "s"):
            center = random.randint(room[1]+2,room[3]-2)
            left = min(door1[0], door2[0])
            right = max(door1[0], door2[0])
            for x in range(left, right+1):
                self.level.floortiles[x][center] = floor
            for y in range(min(door1[1]+1,center),max(door1[1]-1,center)+1):
                self.level.floortiles[door1[0]][y] = floor
            for y in range(min(door2[1]+1,center),max(door2[1]-1,center)+1):
                self.level.floortiles[door2[0]][y] = floor
        if d1o in ("e", "w") and d2o in ("e", "w"):
            center = random.randint(room[0]+1,room[2]-1)
            top = min(door1[1], door2[1])
            bottom = max(door1[1], door2[1])
            for y in range(top, bottom+1):
                self.level.floortiles[center][y] = floor
            for x in range(min(door1[0]+1,center),max(door1[0]-1,center)+1):
                self.level.floortiles[x][door1[1]] = floor
            for x in range(min(door2[0]+1,center),max(door2[0]-1,center)+1):
                self.level.floortiles[x][door2[1]] = floor
        #cases:
        # d1o == d2o: need u-loop
        # 
                       
    def make_box(self, xmin, ymin, xmax, ymax):
        wall = self.data.floordict["wall"]
        for x in range(xmin, xmax+1):
            self.level.floortiles[x][ymin] = wall
            self.level.floortiles[x][ymax] = wall
        for y in range(ymin, ymax+1):
            self.level.floortiles[xmin][y] = wall
            self.level.floortiles[xmax][y] = wall

        xps = max(0, xmax - xmin - 7)
        yps = max(0, ymax - ymin - 7)
        if random.randint(0, 5) > xps: xps = 0
        if random.randint(0, 4) > yps: yps = 0
    
        if xps == 0 and yps == 0:
            box = (xmin, ymin, xmax, ymax)
            self.boxes.append(box)
            for x in range(xmin+1,xmax):
                for y in range(ymin+1,ymax):
                    self.regions[x][y] = box
        elif yps == 0:
            split = random.randint(xmin + 4, xmax - 4)
            self.make_box(xmin, ymin, split, ymax)
            self.make_box(split, ymin, xmax, ymax)
        elif xps == 0:
            split = random.randint(ymin + 4, ymax - 4)
            self.make_box(xmin, ymin, xmax, split)
            self.make_box(xmin, split, xmax, ymax)
        else:
            if random.randint(0, xps + yps - 1) < xps:
                split = random.randint(xmin + 4, xmax - 4)
                self.make_box(xmin, ymin, split, ymax)
                self.make_box(split, ymin, xmax, ymax)
            else:
                split = random.randint(ymin + 4, ymax - 4)
                self.make_box(xmin, ymin, xmax, split)
                self.make_box(xmin, split, xmax, ymax)
        
def generate(level, state, data, depth):
    t = time.clock()
    LevelGen(level, state, data, depth).generate()
    #print time.clock() - t
